package fons.navigator.views.piemap;


import java.awt.geom.Point2D;
import java.util.Comparator;
import java.util.Set;

import org.apache.log4j.Logger;

import edu.umd.cs.piccolo.PCamera;
import edu.umd.cs.piccolo.PNode;
import edu.umd.cs.piccolo.event.PBasicInputEventHandler;
import edu.umd.cs.piccolo.event.PInputEvent;
import edu.umd.cs.piccolo.event.PInputEventListener;
import fons.model.ontology.Context;
import fons.model.ontology.Dimension;
import fons.model.ontology.Filters;
import fons.model.ontology.SelectionListener;
import fons.model.ontology.ValueSpace;
import fons.navigator.util.GeomUtils;
import fons.navigator.util.piccolo.PNodes;
import fons.util.Disposable;


public class Target extends PNode implements SelectionListener, Disposable {
	private static final String SELECTION_CATEGORY_TARGETS = "TARGETS";
	private static final long serialVersionUID = 1L;
	private static final Logger logger = Logger.getLogger(Target.class);

	private final PieMap pieMap;
	private final Dimension target;
	private final Point2D orig;
	
	private ValueSpace currentTargetSpace;
	private DrawingStrategy drawingStrategy;

	public Target(PieMap visualPieMap, Dimension target, ValueSpace targetSpace,
					  double origX, double origY) {
		if (Double.isNaN(origX)  ||  Double.isNaN(origY)) {
			logger.error("Koordinaten f&uuml;r " + target.getName() + " sind nicht definiert");
			throw new IllegalArgumentException("NaN coordinates passed in for node " + target);
		}
		this.orig = new Point2D.Double(origX, origY);
		this.target = target;
		this.currentTargetSpace = targetSpace;
		this.pieMap = visualPieMap;
		addInputEventListener(INPUT_EVENT_HANDLER);
		newDrawingStrategy(CircleDrawingStrategy.class);
		visualPieMap.getAbstractView().getContext().getStandardSelection().addSelectionListener(target, this);
	}

	public final Point2D getPoint() {
		return orig;
	}

	protected final void updatePositionInCamera(PCamera cam) {
		Point2D p = getPoint();
		setVisible(cam.getViewBounds().contains(p));
		
		if (getVisible()) {
			p = cam.viewToLocal(new Point2D.Double(p.getX(), p.getY()));
			this.setOffset(p.getX(), p.getY());
		}
	}

	public final double getProjectedLongitude() {
		return orig.getX();
	}

	public final double getProjectedLatitude() {
		return orig.getY();
	}

	private static final PInputEventListener INPUT_EVENT_HANDLER = new PBasicInputEventHandler() {
		@Override
		public void mouseClicked(PInputEvent event) {
			Target vnode = PNodes.getAncestorOfType(event.getPickedNode(), Target.class);
			if (vnode == null) return;
			if (event.getClickCount() == 1) {
				if (vnode.isSelected()) {
					vnode.getContext().getStandardSelection().unselect(SELECTION_CATEGORY_TARGETS, vnode.getTargetDimension());
				} else {
					vnode.getContext().getStandardSelection().select(SELECTION_CATEGORY_TARGETS, vnode.getTargetDimension());
				}

				if (vnode.pieMap.showSelectedOnly()) {
					vnode.pieMap.updateDynamicPositions();
				}
			}
		}
		
		@Override
		public void mouseEntered(PInputEvent event) {
			Target vnode = PNodes.getAncestorOfType(event.getPickedNode(), Target.class);
			if (vnode == null) return;
			else {
				vnode.moveToFront();
			}
		}

	};

	public boolean isSelected() {
		return getContext().getStandardSelection().isSelected(SELECTION_CATEGORY_TARGETS, target);
	}

	public void updateLegend() {
		if (pieMap.getShowLegends()) {
			drawingStrategy.showLegend();
		} else {
			drawingStrategy.hideLegend();
		}
	}

	@Override
	public String toString() {
		return "VisualNode{" +
				"label=" + getTargetDimension().toString() +
				'}';
	}

	public double distanceTo(Target node) {
		return GeomUtils.distance(getProjectedLongitude(), getProjectedLatitude(), node.getProjectedLongitude(), node.getProjectedLatitude());
	}

	public boolean hasValues() {
		return currentTargetSpace.filter(Filters.anyOf(pieMap.getParts())
				 .and(Filters.anyOf(pieMap.getSubParts())))
				 .size() > 0;
	}

	public static final Comparator<Target> LABEL_COMPARATOR = new Comparator<Target>() {
		public int compare(Target o1, Target o2) {
			return Dimension.COMPARE_BY_DEFINITION.compare(o1.getTargetDimension(), o2.getTargetDimension());
		}
	};

	public DrawingStrategy getDrawingStrategy () {
		return drawingStrategy;
	}

	public final PieMap getPieMap () {
		return pieMap;
	}

	@Override
	public void select(String category, Dimension dimension) {
		if (SELECTION_CATEGORY_TARGETS.equals(category)) {
			newDrawingStrategy(PieChartDrawingStrategy.class);
		}
	}

	@Override
	public void unselect(String category, Dimension dimension) {
		if (SELECTION_CATEGORY_TARGETS.equals(category)) {
			if (pieMap.showSelectedOnly()) {
				newDrawingStrategy(CircleDrawingStrategy.class);
			}
		}
	}
	
	public <T extends DrawingStrategy> void newDrawingStrategy (Class<T> clazz) {
		if (drawingStrategy != null) {
			removeChild(drawingStrategy);
			drawingStrategy.removePlacer(pieMap.getDynamicPlacer());
		}
		
		if (this.hasValues()) {
			final DrawingStrategy oldStrategy = drawingStrategy;
			try {
				drawingStrategy = clazz.getConstructor(Target.class, PNode.class)
						.newInstance(Target.this, oldStrategy);
			} catch (Exception e) {
				e.printStackTrace();
				drawingStrategy = new NullDrawingStrategy(this, oldStrategy);
			}
		} else {
			drawingStrategy = new NullDrawingStrategy(this, null);
		}
		
		updateLegend();
		drawingStrategy.setPlacer(pieMap.getDynamicPlacer());
		addChild(drawingStrategy);
		drawingStrategy.zOrder();
	}

	@Override
	public void dispose() {
		getContext().getStandardSelection().removeSelectionListener(target, this);
	}

	public Dimension getTargetDimension() {
		return this.target;
	}

	public void setTargetSpace(ValueSpace subSpace) {
		this.currentTargetSpace = subSpace;
	}

	public ValueSpace getTargetSpace() {
		return currentTargetSpace;
	}

	public Set<Dimension> getPartDimensions() {
		return pieMap.getParts();
	}
	
	public Set<Dimension> getSubParts() {
		return pieMap.getSubParts();
	}
	
	public Dimension getPieDimension() {
		return pieMap.getPieDimension();
	}

	public Context getContext() {
		return pieMap.getContext();
	}
}


